#!/usr/bin/env python
# -*- coding: utf-8 -*-

# earlbot, a simply python silc bot
# Copyright (c) 2010 Jamon Camisso
# <jamonation@gmail.com>
# http://jamon.ca
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program, see enclosed file gpl-3.txt or browse to
# <http://www.gnu.org/licenses/gpl.txt>

#these are our global imports
import os
import sys
import silc
import time
import datetime
import sqlite3
import re
import ConfigParser
from signal import SIGTERM

#these are our local imports
import auth
import moduleloader
import messageparser

#grab values from earlbot.cfg and import them for later use
config = ConfigParser.ConfigParser()
config.read([os.path.expanduser('earlbot.cfg')])

DEBUG = config.get('bot','DEBUG')
NICK = config.get('bot','NICK')
USER = config.get('bot','USER')
NAME = config.get('bot','NAME')
ROOM = config.get('bot','ROOM')
SERVER = config.get('bot','SERVER')
KEYS = config.get('bot','KEYS')
TRIGGER = config.get('bot','TRIGGER')
MODULES = config.get('bot','MODULES')
DB = config.get('bot','DB')
PUB,PRV = KEYS.split(",")
OWNER = config.get('bot','OWNER')
SECRET = config.get('bot','SECRET')
PIDFILE = config.get('bot','PIDFILE')
LOGFILE = config.get('bot','LOGFILE')

#looks at modules set for importing in earlbot.cfg (via MODULES variable) and sends that list off to moduleloader.py for loading, will also reload modules given proper reload private message
modules = moduleloader.load_modules(MODULES, DEBUG)

#our main earlbot class
class earlbot(silc.SilcClient):

    #required
    def running(self):
	bot.connect_to_server(SERVER, 706)

    #required
    def connected(self):
	for room in ROOM.split(','):
	    bot.command_call("JOIN %s" %(room))

    #required
    def failure(self):
        print "* FAILED!"

    #basic greeter, says hi to new users
    #TODO: turn off greetings for already seen users in a channel e.g. after a channel/router split
    def notify_join(self, user, channel):
        if user.nickname != NICK:
	    self.send_channel_message(channel, "Hello %s, welcome to %s!" %(user.nickname, channel))

    #for parsing and sending private messages, e.g. to administer the bot
    #authentication scheme is in auth.py and relies on stored sender.fingerprint hash
    def private_message(self, sender, flags, message):

        auth_token = auth.check_auth(self, sender, DB, DEBUG, SECRET)

	if message.startswith('identify: '):
	    owner_fingerprint = auth.create_auth(self, sender, flags, message, OWNER, SECRET, DB, DEBUG)

	    #reload all loaded modules, handy for hacking on a live earlbot
	if message.startswith('reload') and auth_token == True:
	    try:
		 if DEBUG == True == True:
			print sender.nickname + " requested module reload"
	        moduleloader.reload_modules(MODULES, DEBUG)
	    except:
		 if DEBUG == True == True:
			print 'could not reload %s module' %(message)

        if message.startswith('join '):
            message = message.replace('join ', '')
            channel_join = message.split()[0]
            try:
                self.command_call('join %s' %(channel_join))
                 if DEBUG == True == True:
                    print 'joined channel %s' %(channel_join)
            except:
                print 'couldn\'t join channel %s' %(channel_join)

	#renaming the bot e.g. /msg earlbot TRIGGERnick bewname
        if message.startswith('nick ') and auth_token == True:
        	message = message.replace('nick ', '')
                try:
                    self.command_call('nick %s' %(message))
                except:
                    print 'could not change nick to %s' %(message)

    #main parser for public channel messages
    def channel_message(self, sender, channel, flags, message):

	#send any message from any channel to all loaded and running modules
        messageparser.message(self, sender, channel, flags, message, modules, DB, DEBUG, NICK)

def daemonize(stdout='/dev/null', stderr=None, stdin='/dev/null', pidfile=None, startmsg = 'started with pid %s' ):
        '''
		From http://code.activestate.com/recipes/66012/:
                This forks the current process into a daemon.
                The stdin, stdout, and stderr arguments are file names that
                will be opened and be used to replace the standard file descriptors
                in sys.stdin, sys.stdout, and sys.stderr.
                These arguments are optional and default to /dev/null.
                Note that stderr is opened unbuffered, so
                if it shares a file with stdout then interleaved output
                may not appear in the order that you expect.
        '''
        # Do first fork.
        try:
                pid = os.fork()
                if pid > 0: sys.exit(0) # Exit first parent.
        except OSError, e:
                sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
                sys.exit(1)

        # Decouple from parent environment.
        os.chdir("/")
        os.umask(0)
        os.setsid()
        ## Do second fork.
        try:
                pid = os.fork()
                if pid > 0: sys.exit(0) # Exit second parent.
        except OSError, e:
                sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
                sys.exit(1)

        # Open file descriptors and print start message
        if not stderr: stderr = stdout
        si = file(stdin, 'r')
        so = file(stdout, 'a+')
        se = file(stderr, 'a+', 0)
        pid = str(os.getpid())
        sys.stdout.write("%s\n" % startmsg % pid)
        sys.stdout.flush()
        if pidfile: file(pidfile,'w+').write("%s\n" % pid)

        # Redirect standard file descriptors.
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

def startstop(stdout='/dev/null', stderr=None, stdin='/dev/null', pidfile=PIDFILE, startmsg = 'started with pid %s' ):
	''' This function also from http://code.activestate.com/recipes/66012/'''
        if len(sys.argv) > 1:
                action = sys.argv[1]
                try:
                        pf  = file(pidfile,'r')
                        pid = int(pf.read().strip())
                        pf.close()
                except IOError:
                        pid = None

                if 'stop' == action or 'restart' == action:
                        if not pid:
                                mess = "Could not stop, pid file '%s' missing.\n"
                                sys.stderr.write(mess % pidfile)
                        else:
                                try:
                                        while True: 
                                                os.kill(pid,SIGTERM) 
                                                time.sleep(1)
                                except OSError, err: 
                                        err = str(err)
                                        if err.find("No such process") > 0: 
                                                os.remove(pidfile) 
                                                if os.path.isfile(stdout):
                                                        now = datetime.datetime.now()
                                                        os.rename(stdout, stdout + '.' + now.strftime("%Y%m%d%H%M%S") + str(now.microsecond))
                                        else:
                                                print str(err)
                                                sys.exit(1)

                if 'stop' == action:
                        sys.exit(0)

                if 'restart' == action:
                        action = 'start'
                        pid = None

                if 'start' == action:
                        if pid:
                                mess = "Start aborted since pid file '%s' exists.\n"
                                sys.stderr.write(mess % pidfile)
                                sys.exit(1)
                        daemonize(stdout,stderr,stdin,pidfile,startmsg)
                        return
        print "usage: %s start|stop|restart" % sys.argv[0]
        sys.exit(2)

if __name__ == "__main__":

    if len(sys.argv) >= 3:
        if (sys.argv[2] == "-D"):
            startstop(stdout=LOGFILE, pidfile=PIDFILE)
    if (sys.argv[1] == "stop"):
        startstop(stdout=LOGFILE, pidfile=PIDFILE)
    keys = silc.create_key_pair(PUB, PRV, passphrase = '')
    bot = earlbot(keys, NICK, USER, NAME)

    while sys.argv[1] == "start":
        try:
            bot.run_one()
            time.sleep(0.2)
        except KeyboardInterrupt:
            break
